/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * Script.c
 *
 * A few general functions for the script library
 */

#include "lib/framework/frame.h"
#include "script.h"
#include <assert.h>


// Flags for storing function indexes
#define FUNC_SETBIT	0x80000000		// set for a set function, clear otherwise
#define FUNC_LISTMASK	0x70000000
#define FUNC_INDEXMASK	0x0fffffff
#define FUNC_INSTINCT	0x00000000		// instinct function
#define FUNC_CALLBACK	0x40000000		// callback function
#define FUNC_OBJVAR	0x10000000		// object variable
#define FUNC_EXTERNAL	0x20000000		// external variable


// Initialise the script library
BOOL scriptInitialise()
{
	if (!stackInitialise())
	{
		return false;
	}
	if (!interpInitialise())
	{
		return false;
	}
	if (!eventInitialise())
	{
		return false;
	}

	return true;
}

// Shutdown the script library
void scriptShutDown(void)
{
	eventShutDown();
	stackShutDown();
}


/* Free a SCRIPT_CODE structure */
void scriptFreeCode(SCRIPT_CODE *psCode)
{
	UDWORD	i,j;

	debug(LOG_WZ, "Unloading script data");


	/* Free local vars */
	for(i=0; i < psCode->numEvents; i++)
	{
		if(psCode->numLocalVars[i] > 0)		//only free if any defined
		{
			//free strings for event i
			for(j=0; j < psCode->numLocalVars[i]; j++)
			{

				if(psCode->ppsLocalVarVal[i][j].type == VAL_STRING)	//if a string
				{
					if(psCode->ppsLocalVarVal[i][j].v.sval != NULL)		//doublecheck..
					{
						free(psCode->ppsLocalVarVal[i][j].v.sval);		//free string
					}
				}
			}

			free(psCode->ppsLocalVars[i]);
			free(psCode->ppsLocalVarVal[i]);	//free pointer to event i local vars
		}
	}

	free(psCode->pCode);
	if (psCode->pTriggerTab)
	{
		free(psCode->pTriggerTab);
	}
	if (psCode->psTriggerData)
	{
		free(psCode->psTriggerData);
	}
	free(psCode->pEventTab);
	free(psCode->pEventLinks);
	if (psCode->pGlobals != NULL)
	{
		free(psCode->pGlobals);
	}
	if (psCode->psArrayInfo != NULL)
	{
		free(psCode->psArrayInfo);
	}
	if (psCode->psDebug)
	{
		for(i=0; i<psCode->debugEntries; i++)
		{
			if (psCode->psDebug[i].pLabel)
			{
				free(psCode->psDebug[i].pLabel);
			}
		}
		free(psCode->psDebug);
	}
	if (psCode->psVarDebug)
	{
		for(i=0; i<psCode->numGlobals; i++)
		{
			if (psCode->psVarDebug[i].pIdent)
			{
				free(psCode->psVarDebug[i].pIdent);
			}
		}
		free(psCode->psVarDebug);
	}
	if (psCode->psArrayDebug)
	{
		for(i=0; i<psCode->numArrays; i++)
		{
			if (psCode->psArrayDebug[i].pIdent)
			{
				free(psCode->psArrayDebug[i].pIdent);
			}
		}
		free(psCode->psArrayDebug);
	}



	if(psCode->numParams != NULL)
		free(psCode->numParams);

	if(psCode->numLocalVars != NULL)
		free(psCode->numLocalVars);

	if(psCode->ppsLocalVars != NULL)
		free(psCode->ppsLocalVars);

	if(psCode->ppsLocalVarVal != NULL)
		free(psCode->ppsLocalVarVal);

	psCode->numEvents = 0;

	free(psCode);
}


/* Lookup a script variable */
BOOL scriptGetVarIndex(SCRIPT_CODE *psCode, char *pID, UDWORD *pIndex)
{
	UDWORD	index;

	if (!psCode->psVarDebug)
	{
		return false;
	}

	for(index=0; index<psCode->numGlobals; index++)
	{
		if (strcmp(psCode->psVarDebug[index].pIdent, pID)==0)
		{
			*pIndex = index;
			return true;
		}
	}

	return false;
}

/* returns true if passed INTERP_TYPE is used as a pointer in INTERP_VAL, false otherwise.
   all types are listed explicitly, with asserts/warnings for invalid/unrecognised types, as
   getting this wrong will cause segfaults if sizeof(void*) != sizeof(SDWORD) (eg. amd64). a lot of
   these aren't currently checked for, but it's a lot clearer what's going on if they're all here */
BOOL scriptTypeIsPointer(INTERP_TYPE type)
{
	ASSERT( ((type < ST_MAXTYPE) || (type >= VAL_REF)), "scriptTypeIsPointer: invalid type: %d", type );
	// any value or'ed with VAL_REF is a pointer
	if (type >= VAL_REF) return true;
	switch (type) {
		case VAL_STRING:
		case VAL_OBJ_GETSET:
		case VAL_FUNC_EXTERN:
		case ST_INTMESSAGE:
		case ST_BASEOBJECT:
		case ST_DROID:
		case ST_STRUCTURE:
		case ST_FEATURE:
		case ST_TEMPLATE:
		case ST_TEXTSTRING:
		case ST_LEVEL:
		case ST_RESEARCH:
		case ST_GROUP:
		case ST_POINTER_O:
		case ST_POINTER_T:
		case ST_POINTER_S:
		case ST_POINTER_STRUCTSTAT:
			return true;
		case VAL_BOOL:
		case VAL_INT:
		case VAL_FLOAT:
		case VAL_TRIGGER:
		case VAL_EVENT:
		case VAL_VOID:
		case VAL_OPCODE:
		case VAL_PKOPCODE:
		case ST_BASESTATS:
		case ST_COMPONENT:
		case ST_BODY:
		case ST_PROPULSION:
		case ST_ECM:
		case ST_SENSOR:
		case ST_CONSTRUCT:
		case ST_WEAPON:
		case ST_REPAIR:
		case ST_BRAIN:
		case ST_STRUCTUREID:
		case ST_STRUCTURESTAT:
		case ST_FEATURESTAT:
		case ST_DROIDID:
		case ST_SOUND:
			return false;
		default:
			ASSERT(false, "scriptTypeIsPointer: unhandled type: %d", type );
			return false;
	}
}


static const struct {
	INTERP_TYPE type;
	const char *name;
} typeToStringMap[] = {
	// Basic types
	{ VAL_BOOL, "bool" },
	{ VAL_INT, "int" },
	{ VAL_FLOAT, "float" },
	{ VAL_STRING, "string" },

	// events and triggers
	{ VAL_TRIGGER, "trigger" },
	{ VAL_EVENT, "event" },

	{ VAL_VOID, "void" },

	{ VAL_OPCODE, "opcode" },
	{ VAL_PKOPCODE, "pkopcode" },

	{ VAL_OBJ_GETSET, "objgs" },
	{ VAL_FUNC_EXTERN, "func" },

	{ VAL_USERTYPESTART, "usertype" },
	{ VAL_REF, "ref" },
};


const char *scriptTypeToString(INTERP_TYPE type)
{
	int i; // Loop goes down -> signed

	// Look whether it is a defaul type:
	for (i = ARRAY_SIZE(typeToStringMap)-1;
		i >= 0 && type <= typeToStringMap[i].type;
		i--)
	{
		if (type >= typeToStringMap[i].type)
			return typeToStringMap[i].name;
	}

	// Look whether it is a user type:
	if (asScrTypeTab)
	{
		unsigned int i;
		for(i = 0; asScrTypeTab[i].typeID != 0; i++)
		{
			if (asScrTypeTab[i].typeID == type)
			{
				return asScrTypeTab[i].pIdent;
			}
		}
	}

	return "unknown";
}


static const struct {
	OPCODE opcode;
	const char *name;
} opcodeToStringMap[] = {
	{ OP_PUSH, "push" },
	{ OP_PUSHREF, "push(ref)" },
	{ OP_POP, "pop" },

	{ OP_PUSHGLOBAL, "push(global)" },
	{ OP_POPGLOBAL, "pop(global)" },

	{ OP_PUSHARRAYGLOBAL, "push(global[])" },
	{ OP_POPARRAYGLOBAL, "push(global[])" },

	{ OP_CALL, "call" },
	{ OP_VARCALL, "vcall" },

	{ OP_JUMP, "jump" },
	{ OP_JUMPTRUE, "jump(true)" },
	{ OP_JUMPFALSE, "jump(false)" },

	{ OP_BINARYOP, "binary" },
	{ OP_UNARYOP, "unary" },

	{ OP_EXIT, "exit" },
	{ OP_PAUSE, "pause" },

	// The following operations are secondary data to OP_BINARYOP and OP_UNARYOP

	// Maths operators
	{ OP_ADD, "+" },
	{ OP_SUB, "-" },
	{ OP_MUL, "*" },
	{ OP_DIV, "/" },
	{ OP_NEG, "(-)" },
	{ OP_INC, "--" },
	{ OP_DEC, "++" },

	// Boolean operators
	{ OP_AND, "&&" },
	{ OP_OR, "||" },
	{ OP_NOT, "!" },

	//String concatenation
	{ OP_CONC, "&" },

	// Comparison operators
	{ OP_EQUAL, "=" },
	{ OP_NOTEQUAL, "!=" },
	{ OP_GREATEREQUAL, ">=" },
	{ OP_LESSEQUAL, "<=" },
	{ OP_GREATER, ">" },
	{ OP_LESS, "<" },

	{ OP_FUNC, "func" },
	{ OP_POPLOCAL, "pop(local)" },
	{ OP_PUSHLOCAL, "push(local)" },

	{ OP_PUSHLOCALREF, "push(localref)" },
	{ OP_TO_FLOAT, "(float)" },
	{ OP_TO_INT, "(int)" },
};


const char *scriptOpcodeToString(OPCODE opcode)
{
	int i; // Loop goes down -> signed

	// Look whether it is a defaul type:
	for (i = ARRAY_SIZE(opcodeToStringMap)-1;
		i >= 0 && opcode <= opcodeToStringMap[i].opcode;
		i--)
	{
		if (opcode >= opcodeToStringMap[i].opcode)
			return opcodeToStringMap[i].name;
	}

	return "unknown";
}


const char *scriptFunctionToString(SCRIPT_FUNC function)
{
	// Search the instinct functions
	if (asScrInstinctTab)
	{
		unsigned int i;
		for(i = 0; asScrInstinctTab[i].pFunc != NULL; i++)
		{
			if (asScrInstinctTab[i].pFunc == function)
			{
				return asScrInstinctTab[i].pIdent;
			}
		}
	}

	// Search the callback functions
	if (asScrCallbackTab)
	{
		unsigned int i;
		for(i = 0; asScrCallbackTab[i].type != 0; i++)
		{
			if (asScrCallbackTab[i].pFunc == function)
			{
				return asScrCallbackTab[i].pIdent;
			}
		}
	}

	return "unknown";
}

